Library tidyverse laden, bitte zunächst installieren über “Tools” => “Install Packages”
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2 ✓ purrr 0.3.4
## ✓ tibble 3.0.4 ✓ dplyr 1.0.2
## ✓ tidyr 1.1.2 ✓ stringr 1.4.0
## ✓ readr 1.4.0 ✓ forcats 0.5.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
Wir nutzen den Book Crossing Datensatz. Die Daten müssen zuerst geladen, entpackt und dann RStudio zur Verfügung gestellt werden. Die Befehle zum Laden der Dateien können einfach aus dem “Import Dataset”-Menü kopiert werden
BX_Book_Ratings <- read_delim("BX-CSV-Dump/BX-Book-Ratings.csv",
";", escape_double = FALSE, trim_ws = TRUE)
##
## ── Column specification ────────────────────────────────────────────────────────
## cols(
## `User-ID` = col_double(),
## ISBN = col_character(),
## `Book-Rating` = col_double()
## )
BX_Books <- read_delim("BX-CSV-Dump/BX-Books.csv",
";", escape_double = FALSE, trim_ws = TRUE)
##
## ── Column specification ────────────────────────────────────────────────────────
## cols(
## ISBN = col_character(),
## `Book-Title` = col_character(),
## `Book-Author` = col_character(),
## `Year-Of-Publication` = col_double(),
## Publisher = col_character(),
## `Image-URL-S` = col_character(),
## `Image-URL-M` = col_character(),
## `Image-URL-L` = col_character()
## )
## Warning: 38 parsing failures.
## row col expected actual file
## 6451 Year-Of-Publication a double John Peterman 'BX-CSV-Dump/BX-Books.csv'
## 6451 NA 8 columns 9 columns 'BX-CSV-Dump/BX-Books.csv'
## 43666 Year-Of-Publication a double \"Freedom Song\"" 'BX-CSV-Dump/BX-Books.csv'
## 43666 NA 8 columns 10 columns 'BX-CSV-Dump/BX-Books.csv'
## 51750 Year-Of-Publication a double Frank Muir 'BX-CSV-Dump/BX-Books.csv'
## ..... ................... ......... ................... ..........................
## See problems(...) for more details.
BX_Users <- read_delim("BX-CSV-Dump/BX-Users.csv",
";", escape_double = FALSE, trim_ws = TRUE)
##
## ── Column specification ────────────────────────────────────────────────────────
## cols(
## `User-ID` = col_double(),
## Location = col_character(),
## Age = col_character()
## )
Wir benötigen nicht alle Spalten aus dem Bücher-Dataframe und schreiben das Ergebnis in einen neuen Dataframe:
(books <- BX_Books %>%
select(ISBN,`Book-Title`,`Book-Author`,`Year-Of-Publication`,Publisher))
Um Arbeitsspeicher zu sparen, löschen wir den alten Dataframe (das sollten wir nur tun, wenn wir uns sicher sind, dass wir wirklich nichts mehr aus dem alten Dataframe benötigen). Wir hätten auch einfach die Transformation in das gleiche Objekt schreiben können (BX_Books <- BX_Books), aber der neue Name ist einfacher :) rm steht übrigens für remove:
rm(BX_Books)
In dem Users-Datensatz wird Zunächst einmal das Alter von Character auf Numerisch geändert. Das Ergebnis schreiben wir in einen neuen DataFrame:
users <- BX_Users %>%
mutate(Age = as.numeric(Age))
## Warning: Problem with `mutate()` input `Age`.
## ℹ NAs durch Umwandlung erzeugt
## ℹ Input `Age` is `as.numeric(Age)`.
## Warning in mask$eval_all_mutate(dots[[i]]): NAs durch Umwandlung erzeugt
Die Meldung “Problem with mutate() input Age. ℹ NAs introduced by coercion” sieht gefährlich aus, und in manchen Fällen kann sie es auch sein. Da wir von Character auf Numerisch konvetieren, beschwert sich R, dass aus NULL keine Zahl gemacht werden kann. Auch hier schreiben wir das Ergebnis in einen neuen Dataframe und löschen den alten.
rm(BX_Users)
Der Dataframe mit den Ratings wird mit dem Dataframe books gemerged, da wir nicht nur die ISBN haben wollen. Auch hiernach löschen wir den Original-Dataframe, aber erst nachdem wir uns davon überzeugt haben, dass der left_join gut gelaufen ist. Indem ich den Ausdruck in Klammern setze, kann ich das Ergebnis sofort sehen:
(ratings <- BX_Book_Ratings %>%
left_join(books))
## Joining, by = "ISBN"
rm(BX_Book_Ratings)
Offensichtlich habe ich einige Daten ohne Titel etc; diese möchte ich nicht in meinem Datensatz behalten.
(ratings <- ratings %>%
filter(!is.na(`Book-Title`)))
Wir schauen uns die Stimmigkeit der Daten an. Zunächst einmal gucken wir, wie viele Ratings die Nutzer abgegeben haben beziehungsweise wie viele Bücher sie gelesen haben (ein Rating von 0 bedeutet, dass der Nutzer kein Rating abgegeben hat):
(ratings_per_user <- ratings %>%
group_by(`User-ID`) %>%
summarize(n = n()) %>%
arrange(desc(n)))
## `summarise()` ungrouping output (override with `.groups` argument)
Ein Nutzer mit vielen 1.000 Ratings/Büchern klingt zunächst einmal verdächtig, aber wir lassen sie drin. Auffallend ist aber, dass wir nur 92.107 Nutzer haben, d.h. anscheinend, dass der Rest angemeldet war, aber nix gemacht hat? Diese werden uns für unsere Auswertung wahrscheinlich nicht helfen, so dass wir sie rausnehmen:
(users <- users %>%
left_join(ratings_per_user) %>%
filter(n > 0))
## Joining, by = "User-ID"
Mit der Stadt können wir auch nicht so viel anfangen, so dass wir uns nur das Land ziehen; hierzu verwenden wir einen regulären Ausdruck:
(users <- users %>%
filter(!is.na(Age)) %>%
mutate(country = str_remove(Location,".*,")) %>%
filter(country != ""))
Wie viele Nutzer haben wir pro Land?
users %>%
group_by(country) %>%
summarize(n = n()) %>%
arrange(desc(n))
## `summarise()` ungrouping output (override with `.groups` argument)
Wir sehen, dass ein paar Nutzer es nicht hinbekommen haben, ihr Land richtig einzugeben und versuchen das mit einem regulären Ausdruck zu bereinigen:
users <- users %>%
mutate(country = str_extract(country, "\\w+"))
Nun schauen wir uns noch die Verteilung an:
(ratings.distribution <- ratings %>%
group_by(`User-ID`) %>%
summarize(n = n()) %>%
arrange(desc(n)) %>%
group_by(n) %>%
summarize(m = n()) )
## `summarise()` ungrouping output (override with `.groups` argument)
## `summarise()` ungrouping output (override with `.groups` argument)
hist(ratings.distribution$n, breaks=100)
ratings %>%
group_by(`Book-Title`) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = mean(`Book-Rating`)) %>%
arrange(desc(dieBestenBuecher))
## `summarise()` ungrouping output (override with `.groups` argument)
Wir sehen, dass diese Sortierung überhaupt keinen Sinn ergibt.
ratings %>%
group_by(`Book-Title`) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = median(`Book-Rating`), wieviele = n()) %>%
arrange(desc(dieBestenBuecher), desc(wieviele)) %>%
filter(wieviele > 10)
## `summarise()` ungrouping output (override with `.groups` argument)
ratings %>%
group_by(`Book-Author`) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = median(`Book-Rating`), wieviele = n()) %>%
arrange(desc(dieBestenBuecher), desc(wieviele)) %>%
filter(wieviele > 10)
## `summarise()` ungrouping output (override with `.groups` argument)
Wir haben hier sehr viele Publisher drin, die nur ein Buch veröffentlicht haben, diese sollten wir rausnehmen. Manche Publisher haben aber auch sehr seltsame Namen, so dass dies auch bereinigt werden sollte.
ratings %>%
group_by(Publisher) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = median(`Book-Rating`), wieviele = n()) %>%
arrange(desc(dieBestenBuecher), desc(wieviele)) %>%
filter(wieviele > 10)
## `summarise()` ungrouping output (override with `.groups` argument)
Das Ergebnis ist ein bisschen unerwartet :)
Wir könnten das auch anders lösen, indem wir in die vorherige Abfrage einfach den Publisher mitaufnehmen:
ratings %>%
group_by(`Book-Title`, Publisher) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = median(`Book-Rating`), wieviele = n()) %>%
arrange(desc(dieBestenBuecher), desc(wieviele)) %>%
filter(wieviele > 10) %>%
ungroup() %>%
select(Publisher) %>%
unique()
## `summarise()` regrouping output by 'Book-Title' (override with `.groups` argument)
Insgesamt stellt sich aber die Frage, was das überhaupt ist, “das Beste”, denn hier geht es nur um Popularität. So könnte es in jedem Land unterschiedliche Präferenzen geben.
ratings %>%
left_join(users) %>%
filter(`Book-Rating` > 0) %>%
filter(country == "germany") %>%
group_by(`Book-Title`) %>%
filter(`Book-Rating` > 0) %>%
summarise(dieBestenBuecher = median(`Book-Rating`), wieviele = n()) %>%
arrange(desc(dieBestenBuecher), desc(wieviele)) %>%
filter(wieviele > 10)
## Joining, by = "User-ID"
## `summarise()` ungrouping output (override with `.groups` argument)